package controller

import (
	"flag"
	"phant-operator/pkg/apis/phant/v1alpha1"
	"phant-operator/pkg/client/clientset/versioned"
	informers "phant-operator/pkg/client/informers/externalversions"
	listers "phant-operator/pkg/client/listers/phant/v1alpha1"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kubeinformers "k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	eventv1 "k8s.io/client-go/kubernetes/typed/core/v1"
	appslisters "k8s.io/client-go/listers/apps/v1"
	batchlisters "k8s.io/client-go/listers/batch/v1"
	corelisterv1 "k8s.io/client-go/listers/core/v1"
	extensionslister "k8s.io/client-go/listers/extensions/v1beta1"
	networklister "k8s.io/client-go/listers/networking/v1"
	storagelister "k8s.io/client-go/listers/storage/v1"
	"k8s.io/client-go/tools/record"
	"k8s.io/klog/v2"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

// init klog default parameters perform like k8s.io/component-base@v0.20.15/logs/logs.go
func init() {
	klog.InitFlags(flag.CommandLine)
}

// CLIConfig is used save all configuration read from command line parameters
type CLIConfig struct {
	PrintVersion bool
	// Port number to use for connection, default 6080
	Port int
	// The number of workers that are allowed to sync concurrently.
	// Larger number = more responsive management, but more CPU
	// (and network) load

	Workers int
	// Controls whether operator should manage kubernetes cluster
	// wide TiDB clusters
	ClusterScoped bool

	ClusterPermissionNode bool
	ClusterPermissionPV   bool
	ClusterPermissionSC   bool
	AutoFailover          bool

	MasterFailoverPeriod time.Duration
	WorkerFailoverPeriod time.Duration
	LeaseDuration        time.Duration
	RenewDeadline        time.Duration
	RetryPeriod          time.Duration
	WaitDuration         time.Duration
	// ResyncDuration is the resync time of informer
	ResyncDuration time.Duration
	// DetectNodeFailure enables detection of node failures for stateful failure pods for recovery
	DetectNodeFailure bool
	// PodHardRecoveryPeriod is the hard recovery period for a failure pod
	PodHardRecoveryPeriod time.Duration
	// Defines whether tidb operator run in test mode, test mode is
	// only open when test
	TestMode bool
	// Selector is used to filter CR labels to decide
	// what resources should be watched and synced by controller
	Selector string

	KubeConfig string
	// KubeClientQPS indicates the maximum QPS to the kubenetes API server from client.
	KubeClientQPS   float64
	KubeClientBurst int
}

// DefaultCLIConfig returns the default command line configuration
func DefaultCLIConfig() *CLIConfig {

	return &CLIConfig{
		Port:          6080,
		Workers:       5,
		ClusterScoped: true,
		AutoFailover:  true,

		MasterFailoverPeriod:  5 * time.Minute,
		WorkerFailoverPeriod:  5 * time.Minute,
		LeaseDuration:         15 * time.Second,
		RenewDeadline:         10 * time.Second,
		RetryPeriod:           2 * time.Second,
		WaitDuration:          5 * time.Second,
		ResyncDuration:        30 * time.Second,
		PodHardRecoveryPeriod: 24 * time.Hour,
		DetectNodeFailure:     false,
		Selector:              "",
	}
}

// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
func (c *CLIConfig) AddFlag(_ *flag.FlagSet) {
	flag.BoolVar(&c.PrintVersion, "V", false, "Show version and quit")
	flag.BoolVar(&c.PrintVersion, "version", false, "Show version and quit")
	flag.IntVar(&c.Port, "port", c.Port, "Port number to use for connection, default 6080.")
	flag.IntVar(&c.Workers, "workers", c.Workers, "The number of workers that are allowed to sync concurrently. Larger number = more responsive management, but more CPU (and network) load")
	flag.BoolVar(&c.ClusterScoped, "cluster-scoped", c.ClusterScoped, "Whether tidb-operator should manage kubernetes cluster wide TiDB Clusters")
	flag.BoolVar(&c.ClusterPermissionNode, "cluster-permission-node", c.ClusterPermissionNode, "Whether tidb-operator should have node permissions even if cluster-scoped is false")
	flag.BoolVar(&c.ClusterPermissionPV, "cluster-permission-pv", c.ClusterPermissionPV, "Whether tidb-operator should have persistent volume permissions even if cluster-scoped is false")
	flag.BoolVar(&c.ClusterPermissionSC, "cluster-permission-sc", c.ClusterPermissionSC, "Whether tidb-operator should have storage class permissions even if cluster-scoped is false")
	flag.BoolVar(&c.AutoFailover, "auto-failover", c.AutoFailover, "Auto failover")
	flag.DurationVar(&c.PodHardRecoveryPeriod, "pod-hard-recovery-period", c.PodHardRecoveryPeriod, "Hard recovery period for a failure pod default(24h)")
	flag.BoolVar(&c.DetectNodeFailure, "detect-node-failure", c.DetectNodeFailure, "Automatically detect node failures")
	flag.DurationVar(&c.ResyncDuration, "resync-duration", c.ResyncDuration, "Resync time of informer")
	flag.BoolVar(&c.TestMode, "test-mode", false, "whether phant-operator run in test mode")
	flag.StringVar(&c.Selector, "selector", c.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='")

	// see https://pkg.go.dev/k8s.io/client-go/tools/leaderelection#LeaderElectionConfig for the config
	flag.DurationVar(&c.LeaseDuration, "leader-lease-duration", c.LeaseDuration, "leader-lease-duration is the duration that non-leader candidates will wait to force acquire leadership")
	flag.DurationVar(&c.RenewDeadline, "leader-renew-deadline", c.RenewDeadline, "leader-renew-deadline is the duration that the acting master will retry refreshing leadership before giving up")
	flag.DurationVar(&c.RetryPeriod, "leader-retry-period", c.RetryPeriod, "leader-retry-period is the duration the LeaderElector clients should wait between tries of actions")
	flag.StringVar(&c.KubeConfig, "kube-config", c.KubeConfig, "The maximum QPS to the kubenetes API server from client")
	flag.Float64Var(&c.KubeClientQPS, "kube-client-qps", c.KubeClientQPS, "The maximum QPS to the kubenetes API server from client")
	flag.IntVar(&c.KubeClientBurst, "kube-client-burst", c.KubeClientBurst, "The maximum burst for throttle to the kubenetes API server from client")
}

// HasNodePermission returns whether the user has permission for node operations.
func (c *CLIConfig) HasNodePermission() bool {
	return c.ClusterScoped || c.ClusterPermissionNode
}

// HasPVPermission returns whether the user has permission for persistent volume operations.
func (c *CLIConfig) HasPVPermission() bool {
	return c.ClusterScoped || c.ClusterPermissionPV
}

// HasSCPermission returns whether the user has permission for storage class operations.
func (c *CLIConfig) HasSCPermission() bool {
	return c.ClusterScoped || c.ClusterPermissionSC
}

type Controls struct {
	//JobControl         JobControlInterface
	ConfigMapControl   ConfigMapControlInterface
	StatefulSetControl StatefulSetControlInterface
	ServiceControl     ServiceControlInterface
	EndpointsControl   EndpointsControlInterface
	//PVCControl        PVCControlInterface
	//GeneralPVCControl GeneralPVCControlInterface
	GenericControl GenericControlInterface
	TypedControl   TypedControlInterface
	//SecretControl     SecretControlInterface
}

// Dependencies is used to store all shared dependent resources to avoid
// pass parameters everywhere.
type Dependencies struct {
	// CLIConfig represents all parameters read from command line
	CLIConfig *CLIConfig
	// Operator client interface
	Clientset versioned.Interface
	// Kubernetes client interface
	KubeClientset kubernetes.Interface
	//GenericClient                  client.Client
	InformerFactory                informers.SharedInformerFactory
	KubeInformerFactory            kubeinformers.SharedInformerFactory
	LabelFilterKubeInformerFactory kubeinformers.SharedInformerFactory
	Recorder                       record.EventRecorder

	// Listers
	ServiceLister        corelisterv1.ServiceLister
	EndpointLister       corelisterv1.EndpointsLister
	PVCLister            corelisterv1.PersistentVolumeClaimLister
	PVLister             corelisterv1.PersistentVolumeLister
	PodLister            corelisterv1.PodLister
	NodeLister           corelisterv1.NodeLister
	SecretLister         corelisterv1.SecretLister
	ConfigMapLister      corelisterv1.ConfigMapLister
	StatefulSetLister    appslisters.StatefulSetLister
	DeploymentLister     appslisters.DeploymentLister
	JobLister            batchlisters.JobLister
	IngressLister        networklister.IngressLister
	IngressV1Beta1Lister extensionslister.IngressLister // in order to be compatibility with kubernetes which less than v1.19
	StorageClassLister   storagelister.StorageClassLister

	PostgresClusterLister listers.PostgresClusterLister

	// Controls
	Controls

	AWSConfig aws.Config
}

// NewDependencies is used to construct the dependencies
func NewDependencies(ns string, cliCfg *CLIConfig, clientset versioned.Interface, kubeClientset kubernetes.Interface, genericCli client.Client) (*Dependencies, error) {
	var (
		options     []informers.SharedInformerOption
		kubeoptions []kubeinformers.SharedInformerOption
	)
	if !cliCfg.ClusterScoped {
		options = append(options, informers.WithNamespace(ns))
		kubeoptions = append(kubeoptions, kubeinformers.WithNamespace(ns))
	}
	tweakListOptionsFunc := func(options *metav1.ListOptions) {
		if len(options.LabelSelector) > 0 {
			options.LabelSelector += ",app.kubernetes.io/managed-by=tidb-operator"
		} else {
			options.LabelSelector = "app.kubernetes.io/managed-by=tidb-operator"
		}
	}
	labelKubeOptions := append(kubeoptions, kubeinformers.WithTweakListOptions(tweakListOptionsFunc))
	tweakListOptionsFunc = func(options *metav1.ListOptions) {
		if len(cliCfg.Selector) > 0 {
			options.LabelSelector = cliCfg.Selector
		}
	}
	options = append(options, informers.WithTweakListOptions(tweakListOptionsFunc))

	// Initialize the informer factories
	informerFactory := informers.NewSharedInformerFactoryWithOptions(clientset, cliCfg.ResyncDuration, options...)
	kubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClientset, cliCfg.ResyncDuration, kubeoptions...)
	labelFilterKubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClientset, cliCfg.ResyncDuration, labelKubeOptions...)

	// Initialize the event recorder
	eventBroadcaster := record.NewBroadcasterWithCorrelatorOptions(record.CorrelatorOptions{QPS: 1})
	eventBroadcaster.StartLogging(klog.V(2).Infof)
	eventBroadcaster.StartRecordingToSink(&eventv1.EventSinkImpl{
		Interface: eventv1.New(kubeClientset.CoreV1().RESTClient()).Events("")})
	recorder := eventBroadcaster.NewRecorder(v1alpha1.Scheme, corev1.EventSource{Component: "phant-controller-manager"})

	deps := newDependencies(cliCfg, clientset, kubeClientset, informerFactory, kubeInformerFactory, labelFilterKubeInformerFactory, recorder)
	deps.Controls = newRealControls(cliCfg, kubeClientset, genericCli, kubeInformerFactory, recorder)
	return deps, nil
}

func newRealControls(
	_ *CLIConfig,
	kubeClientset kubernetes.Interface,
	genericCli client.Client,
	kubeInformerFactory kubeinformers.SharedInformerFactory,
	recorder record.EventRecorder) Controls {

	var (
		serviceLister = kubeInformerFactory.Core().V1().Services().Lister()
		stsLister     = kubeInformerFactory.Apps().V1().StatefulSets().Lister()
		epsLister     = kubeInformerFactory.Core().V1().Endpoints().Lister()

		genericCtrl = NewRealGenericControl(genericCli, recorder)
	)
	return Controls{
		ServiceControl:     NewRealServiceControl(kubeClientset, serviceLister, recorder),
		StatefulSetControl: NewRealStatefuSetControl(kubeClientset, stsLister, recorder),
		EndpointsControl:   NewRealEndpoinControl(kubeClientset, epsLister, recorder),
		ConfigMapControl:   NewRealConfigMapControl(kubeClientset, recorder),
		TypedControl:       NewTypedControl(genericCtrl),
	}
}

func newDependencies(cliCfg *CLIConfig,
	clientset versioned.Interface,
	kubeClientset kubernetes.Interface,
	informerFactory informers.SharedInformerFactory,
	kubeInformerFactory kubeinformers.SharedInformerFactory,
	labelFilterKubeInformerFactory kubeinformers.SharedInformerFactory,
	recorder record.EventRecorder) *Dependencies {
	var (
		nodeLister corelisterv1.NodeLister
		pvLister   corelisterv1.PersistentVolumeLister
		scLister   storagelister.StorageClassLister
		//ingLister  networklister.IngressLister
	)
	if cliCfg.HasNodePermission() {
		nodeLister = kubeInformerFactory.Core().V1().Nodes().Lister()
	} else {
		klog.Info("no permission for nodes, skip creating node lister")
	}
	if cliCfg.HasPVPermission() {
		pvLister = kubeInformerFactory.Core().V1().PersistentVolumes().Lister()
	} else {
		klog.Info("no permission for persistent volumes, skip creating pv lister")
	}
	if cliCfg.HasSCPermission() {
		scLister = kubeInformerFactory.Storage().V1().StorageClasses().Lister()
	} else {
		klog.Info("no permission for storage classes, skip creating sc lister")
	}

	//ingLister = kubeInformerFactory.Networking().V1().Ingresses().Lister()

	/*
		//aws config
		cfg, err := config.LoadDefaultConfig(context.Background())
		if err != nil {
			return nil, fmt.Errorf("can't load aws config: %w", err)
		}
	*/

	return &Dependencies{
		CLIConfig:                      cliCfg,
		InformerFactory:                informerFactory,
		Clientset:                      clientset,
		KubeClientset:                  kubeClientset,
		KubeInformerFactory:            kubeInformerFactory,
		LabelFilterKubeInformerFactory: labelFilterKubeInformerFactory,
		Recorder:                       recorder,

		ServiceLister:      kubeInformerFactory.Core().V1().Services().Lister(),
		EndpointLister:     kubeInformerFactory.Core().V1().Endpoints().Lister(),
		PVCLister:          kubeInformerFactory.Core().V1().PersistentVolumeClaims().Lister(),
		PVLister:           pvLister,
		PodLister:          kubeInformerFactory.Core().V1().Pods().Lister(),
		NodeLister:         nodeLister,
		SecretLister:       kubeInformerFactory.Core().V1().Secrets().Lister(),
		ConfigMapLister:    labelFilterKubeInformerFactory.Core().V1().ConfigMaps().Lister(),
		StatefulSetLister:  kubeInformerFactory.Apps().V1().StatefulSets().Lister(),
		DeploymentLister:   kubeInformerFactory.Apps().V1().Deployments().Lister(),
		StorageClassLister: scLister,
		JobLister:          kubeInformerFactory.Batch().V1().Jobs().Lister(),
		//IngressLister:      ingLister,

		PostgresClusterLister: informerFactory.Phant().V1alpha1().PostgresClusters().Lister(),

		//AWSConfig: cfg,
	}

}
